/*
    This source file is part of GeNa project.
    Copyright © GeNa project 2008
    For the latest info, see http://code.google.com/p/genaproject/ or  http://lumpyproject.forums-rpg.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. */

#ifndef PROPERTY_MANAGER_H_
#define PROPERTY_MANAGER_H_

#include <list>
#include <iostream>
#include <string>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/map.hpp>
#include <boost/archive/basic_archive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/version.hpp>
#include <boost/function.hpp>
using namespace boost::serialization;

//Les internes :
#include "singleton.h"
#include <typeinfo>

//Liste des classes :
class PropertyBase; // classe de base pour les propriétés, définis quelques fonctions qui permettent de manipuler les propriétés
template<typename T, class C> class Property; // cette classe implémente les fonctions pour une propriété de type T de la classe C
class PropertyConfigurationEntryBase; // classe de base pour une entrée du fichier de configuration
template<typename T> class PropertyConfigurationEntry; // classe dérivé, contient le chemin de la propriété, ainsi que la valeur donné par le fichier de configuration
class PropertyManager; // c’est le gestionnaire de propriété à proprement parler

//Classe de base pour une propriété, une grande partie des paramètres sont déclaré void* puisque l’on ne connaît pas leur type exact, c’est à la classe dérivé de les convertir vers le type approprié.
class PropertyBase
{
public:
    virtual bool setPropertyValue(const void* val) = 0; //Change la valeur de la propriété, retourne true si le changement est possible (ie correcte cf Property::CheckFunction)
    virtual void* getPropertyValue() = 0; //Retourne la valeur courante de la propriété
    virtual bool setPropertyValue(PropertyConfigurationEntryBase* val) = 0; //Donne à la propriété la valeur d’une entrée du fichier de configuration
    virtual PropertyConfigurationEntryBase* getPropertyConfigurationEntry() = 0; //Créer une nouvelle entrée pour le fichier de configuration
    virtual void addObj(void* obj) = 0; //Ajoute un objet à la liste des objets à actualisé lors de la modification de la propriété
    virtual void removeObj(void* obj) = 0; //Enlève un objet de la liste

    virtual ~PropertyBase() { } //Rien à faire
};

template<typename T, class C> class Property : public PropertyBase
{
public:
    typedef void (C::*CallBack)(T val); //Pointeur vers une fonction de rappel membre de C, le typedef permet d’alléger les notations, ici le nouveau type s’appelle « CallBack ».
    typedef boost::function1<bool, T&> CheckFunction; //Pointeur vers une fonction permettant de vérifier que la valeur passé en paramètre est correcte (plage de donné, caractères utilisé …)

//Data
    std::string name; //Le nom de la propriété
    std::string className; //Le nom de la classe de la propriété

    T value; //La valeur courante
    T defaultValue; //La valeur par défaut

    T C::*var; //Pointeur vers la variable membre à modifier
    CallBack callback; //Pointeur vers la fonction membre à appeller à chaque modification par setPropertyValue()
    CheckFunction checkFunction; //Pointeur vers une fonction permettant de vérifier qu’une nouvelle valeur est correcte
    bool isLocal; //Indique s’il s’agit d’une propriété locale

// !!! Il ne faut pas oublier que les variables ci-dessus peuvent être NULL, dans ce cas,
// il ne faudra pas essayer par exemple d’actualiser la variable dans chaque classe avec
// la nouvelle valeur si var=NULL, par exemple. Il en est de même pour callback et checkFunction.

    std::list<C*> objs; //Liste des objets qui doivent être actualisé


//Functions
    Property(std::string _className, std::string _name, T _defaultvalue, bool _isLocal=false, T C::*_var = NULL, CallBack callback = NULL, CheckFunction _checkFunction = NULL); //Ici, value = defaultValue, constructeur
    Property(std::string _className, std::string _name, T _defaultvalue, T _value, bool _isLocal=false, T C::*_var = NULL, CallBack callback = NULL, CheckFunction _checkFunction = NULL);
    ~Property() { } //Rien à faire, le destructeur de objs est appelé automatiquement.

    bool setPropertyValue(const void* val); //Modifie la valeur de la propriété,
//il faut penser avant de faire le changement à vérifier que la nouvelle valeur est valide
//avec checkFunction si elle n’est pas nul, puis il faut actualisé toutes les variables en
// modifiant obj->*var et en appelant (obj->*callback)(value)
// pour tout obj dans objs, si isLocal = false.

    void* getPropertyValue(); //Retourne la valeur de la propriété

    bool setPropertyValue(PropertyConfigurationEntryBase* val);
//Idem que plus haut, mais il faut vérifier que val soit du bon type,
//si tout est correct on doit avoir typeid(val) == typeid(PropertyConfigurationEntry<T>) ,
//cette vérification est importante car le fichier de configuration peut avoir été mal modifié par
// l’utilisateur, si c’est bon, on peut obtenir la bonne classe dérivé en convertissant val
// avec dynamic_cast<typename PropertyConfigurationEntry<T>>(val)

    PropertyConfigurationEntryBase* getPropertyConfigurationEntry();
//Créer une nouvelle entrée de fichier de configuration

    void addObj(void* obj); //Rajoute un objet

    void removeObj(void* obj);
//Enlève un objet, pour se faire on parcourt toute la liste objs jusqu’à trouver le bon s’il existe
};



//Classe pour la gestion d’une entrée du fichier de configuration
class PropertyConfigurationEntryBase
{
public:
    template<class Archive> void serialize(Archive &ar, const unsigned int version) //Rien de spéciale à faire, en faite, je ne suis même pas sûr que la fonction soit vraiment nécessaire, Boost.Serialization devrait se débrouiller sans, il faudrait tester.
    {
    }

    virtual ~PropertyConfigurationEntryBase() { } //Rien à faire
};

template<typename T> class PropertyConfigurationEntry : public PropertyConfigurationEntryBase
{
public:
    std::string path; //Le chemin de la propriété, le chemin est la concaténation className.name
    T val; //La valeur de la propriété donné par le fichier de configuration

    template<class Archive> void serialize(Archive &ar, const unsigned int version) //Sérialisation des données
    {
        ar & BOOST_SERIALIZATION_NVP(path);
        ar & BOOST_SERIALIZATION_NVP(val);
    }
};

//Liste à compléter, nécessaire pour pouvoir enregistrer les différents types auprès de Boost.Serialization, elle doit être mise en évidence dans le fichier afin que chacun puisse la compléter si besoin est.
//BOOST_CLASS_EXPORT_GUID(PropertyConfigurationEntry<int>, "propInt")
//BOOST_CLASS_EXPORT_GUID(PropertyConfigurationEntry<unsigned int>, "propUInt")
//BOOST_CLASS_EXPORT_GUID(PropertyConfigurationEntry<char>, "propChar")
//…

//Et enfin le gestionnaire de propriété
class PropertyManager
{
private:
    std::map<std::string, PropertyBase*> props; //Liste des propriétés, rangée par chemin
    std::map<std::string, PropertyConfigurationEntryBase*> propConfigurationEntries; //Liste des entrées du fichier de configuration, rangée par chemin

// !!! Il y a une différence entre les propriétés et les entrées du fichiers de configuration,
// les entrées du fichier de configuration peuvent ne correspondrent à aucune propriété,
// si aucune classe n’a été enregistré encore qui possède une tel propriété ou si l’entrée est
//fausse, les propriétés qui n’ont jamais été modifié et ont donc leur valeur par défaut n’ont
// nullement besoin d’être dans le fichier de configuration.
//D’où on tire la règle : lorsqu’une propriété est modifié, on supprime son entrée si elle existe,
// puis en recrée une nouvelle. Lorsqu’une propriété est créee,
//si une entrée du fichier de configuration possède cette entrée,
//on modifie la valeur de la propriété en conséquence, TOUJOURS avec
// Property::setPropertyValue pour effectué les vérifications nécessaires.


public:
    template<typename T, class C> void addProperty(C *obj, std::string _className, std::string _name, T _defaultvalue, bool _isLocal=false, T C::*_var = NULL, typename Property<T,C>::CallBack _callback = NULL, typename Property<T,C>::CheckFunction _checkFunction = NULL);
//Ajoute une propriété, la marche à suivre est :
//1-Vérifier si la propriété n’existe pas déjà, si c’est le cas on se contente de rajouter obj à la liste de la propriété et l’on s’arrête là
//2-Créer une nouvelle propriété
//3-Vérifie si la propriété possède une entrée dans le fichier de configuration, si ce n’est pas le cas, on s’arrête là
//4-Modifie la valeur de la propriété en fonction du fichier de configuration, si celle-ci est invalide, on peut éventuellement affiché une erreur avec un std::cout

    template<class C> void removeObj(C *obj); //Enlève un obj, doit être systématiquement appelé dans le constructeur de tout obj définissant des propriétés
//L’algo bourrin est le meilleur, on enlève obj de toutes les propriétés même celle où il n’y est pas.

    template<typename T> void setPropertyValue(std::string path, const T &val); //Modifie la valeur de la propriété, si la modification peut se faire, il faut rajouter ou remplacer une entrée dans le fichier de configuration
    template<typename T> T getPropertyValue(std::string path) const; //Retourne la valeur d’une propriété
    template<class Archive> void serialize(Archive &ar, const unsigned int version) //Permet de sérialiser la classe via Boost.Serialization
    {
        ar & make_nvp("props", propConfigurationEntries);
    }

//La classe PropertyManager est un singleton, un singleton est une classe qui n’est instancié
// qu’une seule fois. En effet, il ne sert à rien d’avoir deux gestionnaires de propriétés.
//Pour empêcher que la classe ne soit instancié deux fois,
//on place le constructeur et le destructeur en privé.
//C’est radical, personne ne peux plus instancier la classe sauf une fonction membre de la classe
//et statique. Le prototype de celle-ci est « static PropertyManager & getInstance() ».
// Elle retourne l’instance de la classe et peut être appelé de n’importe où,
//elle remplis deux usages : créer l’instance de la classe si ce n’est pas déjà fait,
// retourner l’instance de la classe. Pour obtenir l’instance de la classe il suffit de faire
//PropertyManager::getInsance() qui retourne une référence vers la classe.
//Pour simplifier la vie, tout cela est implémenter par une macro dans Triatek :
private:
    friend class boost::serialization::access;
    void init() { } //Appelé pendant la création
    void destroy() { } //Appelé pendant la destruction

    MAKE_SINGLETON(PropertyManager)
//Voila la macro, elle implémente le constructeur, le destructeur,
//ainsi que la fonction getInstance
};

//Un petit raccourci pour les notations
#define propMgrInstance MAKE_SINGLETON_PSEUDO_INSTANCE(PropertyManager)
//Ainsi au lieu d’écrire PropertyManager::getInstance(), on écrira propMgrInstance, par exemple :
//propMgrInstance.addProperty("ma_classe","ma_variable", 0, false, ma_classe::ma_variable)

//Implementation


template<typename T, class C> Property<T,C>::Property(std::string _className, std::string _name, T _defaultvalue, bool _isLocal, T C::*_var, CallBack _callback, CheckFunction _checkFunction) //Ici, value = defaultValue, constructeur
{
    className = _className;
    name = _name;
    defaultValue = _defaultvalue;
    value = _defaultvalue;
    isLocal = _isLocal;
    var = _var;
    callback = _callback;
    checkFunction = _checkFunction;
}

template<typename T, class C> Property<T,C>::Property(std::string _className, std::string _name, T _defaultvalue, T _value, bool _isLocal, T C::*_var, CallBack _callback, CheckFunction _checkFunction)
{
    className = _className;
    name = _name;
    defaultValue = _defaultvalue;
    value = _value;
    isLocal = _isLocal;
    var = _var;
    callback = _callback;
    checkFunction = _checkFunction;
}

template<typename T, class C> bool Property<T,C>::setPropertyValue(const void* val)
{

   if(!checkFunction || checkFunction(*(T*)val))
    {
        //modifie la valeur
        value=*(T*)val;
        if(!isLocal)
        {
            for(typename std::list<C*>::iterator iter=objs.begin(); iter!=objs.end(); iter++) //On parcourt tout les éléments
            {
                C* obj = *iter;
                if(var)
                    obj->*var=*(T*)val;
                if(callback)
                    (obj->*callback)(*(T*)val);
            }
        }
        return true;
    }
    else
    return false;

}

template<typename T, class C> void* Property<T,C>::getPropertyValue()
{
    return &value;
}

template<typename T, class C> void Property<T,C>::removeObj(void *obj)
{
    objs.remove((C*)obj);
}

template<typename T, class C> void Property<T,C>::addObj(void* obj)
{
    objs.push_back((C*)obj);
}

template<typename T, class C> bool Property<T,C>::setPropertyValue(PropertyConfigurationEntryBase* val)
{
    try
    {
        PropertyConfigurationEntry<T>* v = dynamic_cast< PropertyConfigurationEntry<T>* >(val);
        return setPropertyValue((void*)&(v->val));
    }
    catch(std::bad_cast)
    {
        return false;
    }
}

template<typename T, class C> PropertyConfigurationEntryBase* Property<T,C>::getPropertyConfigurationEntry()
{
    PropertyConfigurationEntry<T>* pce = new PropertyConfigurationEntry<T>;
    pce->val = value;
    pce->path = className + "." + name;
    return pce;
}






template<typename T, class C> void PropertyManager::addProperty(C* obj, std::string _className, std::string _name, T _defaultvalue, bool _isLocal, T C::*_var, typename Property<T,C>::CallBack _callback, typename Property<T,C>::CheckFunction _checkFunction)
{
    //Ajoute une propriété, la marche à suivre est :

//1-Vérifier si la propriété n’existe pas déjà, si c’est le cas on se contente de rajouter
// obj à la liste de la propriété et l’on s’arrête là

    PropertyBase* &ProP = props[_className+"."+_name];

    if(!ProP)
        ProP = new Property<T,C>(_className, _name, _defaultvalue, _isLocal, _var, _callback, _checkFunction);
    ProP->addObj(obj);

//3-Vérifie si la propriété possède une entrée dans le fichier de configuration, si ce n’est pas le cas, on s’arrête là
//find path
    std::map<std::string, PropertyConfigurationEntryBase*>::iterator iter = propConfigurationEntries.find(_className+"."+_name);
    if(iter != propConfigurationEntries.end())
    {
        //4-Modifie la valeur de la propriété en fonction du fichier de configuration, si celle-ci est invalide, on peut éventuellement affiché une erreur avec un std::cout

        if(!ProP->setPropertyValue(&iter->second))
        {
            std::cout<<"erreur, la valeur de la propriété est obsolète"<<std::endl;
        }

    }

    obj->*_var = *(T*)ProP->getPropertyValue();
}

template<class C> void PropertyManager::removeObj(C* obj)
{
    for(std::map<std::string, PropertyBase*>::iterator iter=props.begin();iter!=props.end();iter++)
    {
        iter->second->removeObj(&obj);
    }
}

template<typename T> void PropertyManager::setPropertyValue(std::string path, const T &val)
{
    std::map<std::string, PropertyBase*>::iterator iter=props.find(path);
    bool modifyConf = true;

    if( iter != props.end() && iter->second)
    {
        if(!iter->second->setPropertyValue(&val))
            modifyConf = false;
    }

    if( modifyConf)
    {
        PropertyConfigurationEntryBase* &chgEntryVal = propConfigurationEntries[path];
        PropertyConfigurationEntry<T>* chgEntry;
        if( !chgEntryVal)
        {
            chgEntry = new PropertyConfigurationEntry<T>;
            chgEntry->path = path;
            chgEntryVal  = chgEntry;
        }
        else
            chgEntry = dynamic_cast< PropertyConfigurationEntry<T>* >(chgEntryVal);
        chgEntry->val = val;
    }
}


template<typename T> T PropertyManager::getPropertyValue(std::string path) const
{
    std::map<std::string, PropertyBase*>::const_iterator iter1 = props.find(path);
    if( iter1 != props.end() && iter1->second)
        return *(T*)iter1->second->getPropertyValue();
    std::map<std::string, PropertyConfigurationEntryBase*>::const_iterator iter2 = propConfigurationEntries.find(path);
    if( iter2 != propConfigurationEntries.end() && iter2->second)
        return dynamic_cast< PropertyConfigurationEntry<T>* >(iter2->second)->val;
    return T();
}

#endif //PROPERTY_MANAGER_H_
